#include<X11/X.h>
#include<X11/Xlib.h>
#include <X11/Xatom.h>

#include <iostream>
using namespace std;

// declare / define all global vars
//

struct shutdown {
  ~shutdown () {
    std::cout << FAIL << "!!! din shutdown !!!" << ENDL;
  }
} shdn; // to announce shutdown on quit

std::string APP_NAME = "algowala-0.1"; // in format algowala-x.y.z where x.y.z is version
int SCREEN_WIDTH = 640, SCREEN_HEIGHT = 480, SCREEN_DEPTH = 24; // initial screen width, height & color depth

// see viewwin.cc
int window::PAN_RATE = 100, window::ZOOM_RATE = 100;
double window::PAN_REPEAT = 1. / PAN_RATE, window::ZOOM_REPEAT = 1. / ZOOM_RATE;
float window::PAN_AMOUNT = 0.1, window::ZOOM_AMOUNT = 0.1;

// custom std::vector font
//
font fnt ("jag.fnt");

// console
//

// possible text colors of console
const float cc = 0.6;
const color console::yellow (1, 1, cc);
const color console::green (cc, 1, cc);
const color console::red (1, cc, cc);
const color console::cyan (cc, 1, 1);

console cons;

char xkey; // X window lookedup char for console / field typings (see console.cc / field.cc)

// display - using OpenGL
viewport view;

// keyboard and mouse state
key_state keys;
int mousex = 0, mousey = 0, mouseyy = 0; // absolute mouse x, y & main viewport y
int lmb = 0, rmb = 0, mmb = 0; // mouse buttons

// vars in Tcl interpreter
//

cmdlist cmdlst; 

#define DEFINE_TCL_CMD(X) inline int (X) (ClientData cd, Tcl_Interp* ti, int objc, Tcl_Obj* CONST objv[]) { return tcl_run (cd, ti, objc, objv);}

void add_commands (Tcl_Interp* interp) { // add din commands to Tcl interpreter

  int ncmds = 0;
  tclcmd cmd_funcs [] = {

  };

  command* cmds [] = {
  };

  extern cmdlist cmdlst;
  for (int i = 0; i < ncmds; ++i) {
    command* cmdp = cmds[i];
    cmdlst.add (cmdp);
    Tcl_CreateObjCommand (interp, cmdp->longname.c_str(), cmd_funcs[i], (void *) i, 0);
    Tcl_CreateObjCommand (interp, cmdp->shortname.c_str(), cmd_funcs[i], (void *) i, 0);
  }

  cout << PASS << "+++ added " << ncmds << " din commands to the Tcl interpreter +++" << ENDL;


}

// bring up the Tcl interpreter; all din commands are available in the interpreter.
//
tcl_interp interpreter;

void update_window (int wnow, int hnow, int wprev, int hprev) { // called when main window resized

  glViewport (0, 0, wnow, hnow); // update OpenGL viewport

  view (wnow, hnow); // update viewport data

  int w = wnow - 1, h = hnow - 1;

  // update console window co-ords
  cons.set_window (box<int>(0, -h, w, 0));
  
}

Display *display; // X display

// din window
Window win;
XSetWindowAttributes swa;
XWindowAttributes gwa;

// OpenGL settings for din window
GLint glattribs [] = {GLX_RGBA, GLX_DOUBLEBUFFER, None};
XVisualInfo *visual;
Colormap colormap;
GLXContext  glc;

// X input
XEvent event;

void accept_keypress (int accept) {
  swa.event_mask = ExposureMask | StructureNotifyMask | FocusChangeMask;
  if (accept) {
		swa.event_mask |= KeyPressMask;
		xkey = 0;
	}
  XChangeWindowAttributes (display, win, CWColormap | CWEventMask, &swa);
}

void warp_pointer_abs (int x, int y) {
  XWarpPointer (display, None, win, 0, 0, view.width, view.height, x, y);
}

void warp_pointer (int dx, int dy) {
  XWarpPointer (display, win, None, 0, 0, view.width, view.height, dx, dy);
}

int focus = 0; // din has focus?

int quit_now = 0; // quit now?

int main (int argc, char** argv) {

  // bring up OpenGL window
  //

  display = XOpenDisplay (0);
  if (display == 0) {
    cout << FAIL << "!!! couldnt connect to X server !!!" << ENDL;
    exit (1);
  }

  visual = glXChooseVisual(display, DefaultScreen (display), glattribs);

  if(visual == 0) {
    cout << FAIL << "!!! couldnt setup GLX visual !!!" << ENDL;
    exit (1);
  } else {
    cout << PASS << "+++ opened GLX visual " << (void *) visual->visualid << " for rendering +++" << ENDL;
  }

  colormap = XCreateColormap (display, RootWindow (display, visual->screen), visual->visual, AllocNone);

  swa.colormap = colormap;
  swa.event_mask = ExposureMask | StructureNotifyMask | FocusChangeMask;

  restore_last_window ();
  win = XCreateWindow (display, RootWindow (display, visual->screen), 0, 0, view.width, view.height, 0, visual->depth, InputOutput, visual->visual, CWColormap | CWEventMask, &swa);

  Atom delete_window_message = XInternAtom (display, "WM_DELETE_WINDOW", False);
  XSetWMProtocols (display, win, &delete_window_message, 1);

  glc = glXCreateContext(display, visual, 0, GL_TRUE);
  glXMakeCurrent(display, win, glc);

  XMapWindow(display, win); // show window
  XMoveWindow (display, win, 0, 0);

  extern std::string WIN_TITLE;
  XStoreName (display, win, WIN_TITLE.c_str());

  glClearColor(0, 0, 0, 0); // black bg

  cons.home (); // display all text in console

  // for mouse
  Window rw, cw;
  unsigned int buttons;
  int rx, ry;

  // ui loop	
  while (1) {
		
    if (focus) { 
      XQueryKeymap (display, keys.now); // get keyboard state
      XQueryPointer (display, win, &rw, &cw, &rx, &ry, &mousex, &mousey, &buttons); // get mouse state
      mouseyy = view.ymax - mousey;
      // mouse buttons
      lmb = buttons & Button1Mask;
      rmb = buttons & Button3Mask;
    }

    // handle input
    //

    memcpy (keys.last, keys.now, key_state::NUM_BYTES); // save keyboard state

    glXSwapBuffers (display, win);

    // handle window event
    if ( XPending (display) ) {

      XNextEvent(display, &event);

      if (focus) {
        if (event.type == KeyPress) { // for console typings
          XLookupString (&event.xkey, &xkey, 1, 0, 0);
        } else if (event.type == FocusOut) focus = 0;
      } else if (event.type == FocusIn) focus = 1;

      if(event.type == Expose) { // window resized
        XGetWindowAttributes (display, win, &gwa);
        update_window (gwa.width, gwa.height, view.width, view.height);
      } else if ((event.type == ClientMessage) && (event.xclient.data.l[0] == (unsigned int) delete_window_message)) { // window deleted
				// can only quit thru ESC, ESC again (see ui.cc)
      }

    }

  }

  // glx quit
  glXMakeCurrent(display, None, 0);
  glXDestroyContext(display, glc);
  XDestroyWindow(display, win);
  XCloseDisplay(display);

  return 0;

}
